#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <fstream> //文件流
#include <iostream>
#include <sstream> //可以进行流式的格式化控制操作
#include <sstream>
#include <string>
#include <filesystem> //对文件操作相关的封装
#include "Mutex.hpp"
#include <unistd.h>
#include <memory>
#include<ctime>
#include<cstdio>
using namespace std;

namespace LogModule
{
    using namespace MutexModule;
    const string gsep = "\r\n"; // 默认分隔符,回车换行，分隔符
    // 刷新策略：a.显示器打印  b:向指定文件写入
    class LogStrategy // 刷新策略基类
    {
    public:
        ~LogStrategy() = default;
        //  =0这意味着它是一个纯虚函数，所有派生类都必须对其进行实现。
        virtual void SyncLog(string &message) = 0; // 同步打印日志

    private:
    };
    // 显示器打印日志的策略
    class ConsoleLogStrategy : public LogStrategy // 屏幕打印策略,继承下上面的基类
    {
    public:
        ConsoleLogStrategy()
        {
        }
        // 当在派生类的函数声明末尾添加 override 时，这清晰地表明该函数是对基类中虚函数的重写。
        virtual void SyncLog(string &message) override // 同步打印日志,进行刷新操作
        {
            LockGuard lockguard(_mutex);
            cout << message << gsep; // 直接将信息回车输入到显示器上
        }
        ~ConsoleLogStrategy()
        {
        }

    private:
        Mutex _mutex;
    };
    // 文件打印日志的策略：子类
    string defaultpath = "./log";
    string defaultfile = "my.log";
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const string &path = defaultpath, const string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex); // 创建文件或者是目录的时候保证文件安全
            // 先指定下路径是否存在，不存在则创建，文件不存在的话就进行创建
            if (filesystem::exists(_path)) // 如果这个文件是存在的话，存在的话返回值就是1
            {
                return; // 什么都不用做
            }
            // 下面就是文件路径不存在的话
            try
            {
                filesystem::create_directories(_path);
            }
            catch (const filesystem::filesystem_error &e) // 打印错误信息
            {
                cerr << e.what() << '\n';
            }
        }
        // 输入信息到文本里面去
        virtual void SyncLog(string &message) override // 对这个函数进行重写操作
        {
            // 对文件进行刷新，我们得保证是原子性的刷新操作，所以我们得加锁
            LockGuard lockguard(_mutex);
            string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // 文件名,如果路径最后是斜杠的话，那么我们就等于斜杠什么都不做，但是如果不是斜杠的话那么就加上文件名
            ofstream out(filename.c_str(), ios::app);                           // 以追加写入的方式打开文件
            if (!out.is_open())                                                 // 如果文件打开失败
            {
                return; // 直接返回
            }
            // 这里我们就可以往文件里面去写了
            // 将字符串和字符串的长度传到这个接口里面去
            out << message << gsep; // 写入日志信息,将message和gsep进行连接，然后写入到文件out里面去
            out.close();            // 关闭文件
        }
        ~FileLogStrategy() {}

    private:
        string _path; // 文件路径
        string _file; // 日志文件本身

        Mutex _mutex;
    };

    // 形成一条完整的日志&&根据上面的策略，选择不同的刷新方式

    // 1.形成日志等级
    enum class LogLevel // 枚举常量,将日志的几个等级列出来
    {
        DEBUG,  // 调试信息
        INFO,   // 一般信息
        WARING, // 警告信息
        ERROR,  // 错误信息
        FATAL   // 严重错误信息
    };
    string Level2Str(LogLevel level) // 将Level转化为字符串
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARING:
            return "WARING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }
    string GetTimeStamp() // 获取时间
    {
        //获取当前的时间戳并且将格式转为字符串
        time_t curr=time(nullptr);//time 函数获取当前的时间戳
        //curr 的类型为 time_t，这是一个表示时间的整数类型。

        //结构体变量 curr_tm,用于存储分解后的时间信息
        struct tm  curr_tm;
        localtime_r(&curr,&curr_tm);//localtime_r 函数将时间戳 curr 转换为本地时间，并将结果存储在 curr_tm 中
        char timebuffer[128];
        snprintf(timebuffer,sizeof(timebuffer),"%d-%d-%d %d:%d:%d"
                ,curr_tm.tm_year+1900
                ,curr_tm.tm_mon+1
                ,curr_tm.tm_mday
                ,curr_tm.tm_hour
                ,curr_tm.tm_min
                ,curr_tm.tm_sec);
        //将这些数据格式化到timebuffer中，并返回
        return timebuffer;
    }
    // 1.形成日志&&根据不同的策略，完成刷新
    class Logger // 日志类
    {
    public:
        Logger()
        {
            EnableFileLogStrategy(); // 默认开启文件策略
        }
        void EnableFileLogStrategy(const string &path = defaultpath, const string &file = defaultfile) // 文件策略
        {
            _fflush_strategy = make_unique<FileLogStrategy>();
        }
        void EnableConsoleLogStrategy() // 显示器策略
        {
            // 用于创建并初始化 std::unique_ptr
            _fflush_strategy = make_unique<ConsoleLogStrategy>();
        }
        // 内部类，未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, string &src_name, int line_number, Logger &logger) // 不能进行获取的我们都传进来
                : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger)
            {
                // 日志的左半部分，合并起来
                stringstream ss;
                // 自动进行类型转换，都写到ss中
                ss << " [" << _curr_time << "] "
                   << " [" << Level2Str(_level) << "] " // 将信号等级转换为对应的字符串
                   << " [" << _pid << "] "
                   << " [" << _src_name << "] "
                   << " [" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str(); // 将ss的内容赋值给loginfo
            }
            template <typename T>                 // 重载 << 操作符
            LogMessage &operator<<(const T &info) // 函数模版，说明T可以是任意的类型
            // 我们正常的就是   日志函数<<“hello world”，然后就可以将信息从这个重载的函数传递进来了，并且是可以进行类型处理的
            // 重载的 << 操作符可以处理不同类型的输入参数 info ，比如 int、std::string 等。
            {
                // 日志的右半部分，可变的
                stringstream ss;
                ss << info;
                _loginfo += ss.str(); // 将ss的内容追加到loginfo
                return *this;         // 直接将当前对象进行返回
            }
            ~LogMessage()
            {
                if (_logger._fflush_strategy) // 如果策略存在
                {
                    // 将loginfo的内容进行刷新刷新到指定的位置
                    _logger._fflush_strategy->SyncLog(_loginfo); // 调用策略的刷新日志，

                    // 这里就明白了这里为什么使用内部类，因为内部类可以访问外部类的私有成员
                }
            }

        private:
            string _curr_time; // 当前时间
            LogLevel _level;   // 日志等级
            pid_t _pid;        // 进程ID
            string _message;   // 日志信息
            string _src_name;  // 源文件名字
            int _line_number;  // 日志行号
            string _loginfo;   // 合并之后，一条完整的信息
            Logger &_logger;   // 对象的引用
        };
        //故意写成返回临时对象，次调用该操作符，都会创建一个新的 LogMessage 对象并返回。，然后就会调用<<将数据输入进来
        //当我们的<<没有了，那么我们的临时队形就会调用析构函数自动进行释放操作，然后就会调用策略的刷新日志
        LogMessage operator()(LogLevel level, string name, int line) // 日志函数
        {
            return LogMessage(level,name, line, *this);
        }

        ~Logger()
        {
        }

    private:
        // unique_ptr是 C++ 标准库提供的智能指针，用于管理动态分配的资源（如内存），
        // 确保资源在不再需要时被自动释放，从而避免内存泄漏。
        unique_ptr<LogStrategy> _fflush_strategy; // 日志策略
    };

    Logger logger; // 全局日志对象

    //使用宏，简化用户操作，获取文件名和行号
    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}

#endif